import BatchTable from "../utils/BatchTable"
import FeatureTable from "../utils/FeatureTable"
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"
import * as THREE from "three"
const x = new THREE.Vector3()
const w = new THREE.Vector3()
const C = new THREE.Vector3()
const y = new THREE.Vector3()
const S = new THREE.Quaternion()
const v = new THREE.Vector3()
const M = new THREE.Matrix4()
function _(e) {
  return new TextDecoder().decode(e)
}
class I3DMLoaderBase {
  fetchOptions: any
  workingPath: string
  constructor() {
    ;(this.fetchOptions = {}), (this.workingPath = "")
  }
  resolveExternalURL(e) {
    return /^[^\\/]/.test(e) ? this.workingPath + "/" + e : e
  }
  load(e) {
    return fetch(e, this.fetchOptions)
      .then(t => {
        if (!t.ok) throw new Error(`Failed to load file "${e}" with status ${t.status} : ${t.statusText}`)
        return t.arrayBuffer()
      })
      .then(t => {
        if ("" === this.workingPath) {
          const t = e.split(/\\\//g)
          t.pop(), (this.workingPath = t.join("/"))
        }
        return this.parse(t)
      })
  }
  parse(e) {
    const t = new DataView(e),
      r = String.fromCharCode(t.getUint8(0)) + String.fromCharCode(t.getUint8(1)) + String.fromCharCode(t.getUint8(2)) + String.fromCharCode(t.getUint8(3))
    console.assert("i3dm" === r)
    const s = t.getUint32(4, !0)
    console.assert(1 === s)
    const n = t.getUint32(8, !0)
    console.assert(n === e.byteLength)
    const i = t.getUint32(12, !0),
      o = t.getUint32(16, !0),
      a = t.getUint32(20, !0),
      l = t.getUint32(24, !0),
      c = t.getUint32(28, !0),
      h = e.slice(32, 32 + i + o),
      u = new FeatureTable(h, 0, i, o),
      d = 32 + i + o,
      p = e.slice(d, d + a + l),
      f = new BatchTable(p, u.getData("INSTANCES_LENGTH"), 0, a, l),
      m = d + a + l,
      g = new Uint8Array(e, m, n - m)
    let b = null,
      T = null
    if (c) (b = g), (T = Promise.resolve())
    else {
      const e = this.resolveExternalURL(_(g))
      T = fetch(e, this.fetchOptions)
        .then(t => {
          if (!t.ok) throw new Error(`I3DMLoaderBase : Failed to load file "${e}" with status ${t.status} : ${t.statusText}`)
          return t.arrayBuffer()
        })
        .then(e => {
          b = new Uint8Array(e)
        })
    }
    return T.then(() => ({
      version: s,
      featureTable: u,
      batchTable: f,
      glbBytes: b
    }))
  }
}
export default class I3DMLoader extends I3DMLoaderBase {
  manager: THREE.LoadingManager

  constructor(e = THREE.DefaultLoadingManager) {
    super(), (this.manager = e)
  }
  resolveExternalURL(e) {
    return this.manager.resolveURL(super.resolveExternalURL(e))
  }
  parse(e) {
    return super.parse(e).then(e => {
      const { featureTable: t, batchTable: r } = e,
        s = e.glbBytes.slice().buffer
      return new Promise((e, n) => {
        const i = this.fetchOptions,
          o = this.manager,
          a: any = o.getHandler("path.gltf") || new GLTFLoader(o)
        "include" === i.credentials && "cors" === i.mode && a.setCrossOrigin("use-credentials"),
          "credentials" in i && a.setWithCredentials("include" === i.credentials),
          i.headers && a.setRequestHeader(i.headers)
        let l = this.workingPath
        ;/[\\/]$/.test(l) || (l += "/"),
          a.parse(
            s,
            l,
            s => {
              const n = t.getData("INSTANCES_LENGTH"),
                i = t.getData("POSITION", n, "FLOAT", "VEC3"),
                o = t.getData("NORMAL_UP", n, "FLOAT", "VEC3"),
                a = t.getData("NORMAL_RIGHT", n, "FLOAT", "VEC3"),
                l = t.getData("SCALE_NON_UNIFORM", n, "FLOAT", "VEC3"),
                c = t.getData("SCALE", n, "FLOAT", "SCALAR")
              ;[
                "RTC_CENTER",
                "QUANTIZED_VOLUME_OFFSET",
                "QUANTIZED_VOLUME_SCALE",
                "EAST_NORTH_UP",
                "POSITION_QUANTIZED",
                "NORMAL_UP_OCT32P",
                "NORMAL_RIGHT_OCT32P"
              ].forEach(e => {
                e in t.header && console.warn(`I3DMLoader: Unsupported FeatureTable feature "${e}" detected.`)
              })
              const h = new Map(),
                u = []
              s.scene.traverse(e => {
                if (e.isMesh) {
                  const { geometry: t, material: r } = e,
                    s = new THREE.InstancedMesh(t, r, n)
                  s.position.copy(e.position), s.rotation.copy(e.rotation), s.scale.copy(e.scale), u.push(s), h.set(e, s)
                }
              })
              const d = new THREE.Vector3()
              for (let e = 0; e < n; e++) (d.x += i[3 * e + 0] / n), (d.y += i[3 * e + 1] / n), (d.z += i[3 * e + 2] / n)
              h.forEach((e, t) => {
                const r = t.parent
                r && (r.remove(t), r.add(e), e.updateMatrixWorld(), e.position.copy(d).applyMatrix4(e.matrixWorld))
              })
              for (let e = 0; e < n; e++) {
                y.set(i[3 * e + 0] - d.x, i[3 * e + 1] - d.y, i[3 * e + 2] - d.z),
                  o
                    ? (w.set(o[3 * e + 0], o[3 * e + 1], o[3 * e + 2]),
                      C.set(a[3 * e + 0], a[3 * e + 1], a[3 * e + 2]),
                      x.crossVectors(C, w).normalize(),
                      M.makeBasis(C, w, x),
                      S.setFromRotationMatrix(M))
                    : S.set(0, 0, 0, 1),
                  c ? v.setScalar(c[e]) : l ? v.set(l[3 * e + 0], l[3 * e + 1], l[3 * e + 2]) : v.set(1, 1, 1),
                  M.compose(y, S, v)
                for (let t = 0, r = u.length; t < r; t++) {
                  u[t].setMatrixAt(e, M)
                }
              }
              ;(s.batchTable = r), (s.featureTable = t), (s.scene.batchTable = r), (s.scene.featureTable = t), e(s)
            },
            n
          )
      })
    })
  }
}
